﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Blizzard.Contracts;
using System.Reflection;
using Blizzard.Container;

namespace Blizzard.Interceptors
{
    public class Interceptor //: IInterceptor
    {
        private BlizzardManager _manager;

   
        public Interceptor(BlizzardManager manager)
        {
            _manager = manager;
        }

         

        bool byPass = false;

        public void Intercept(IDataObject mainObject, string propertyName , bool isSetter , object oldValue = null , object newValue = null)
        {

            Type mainType = mainObject.GetType();//.BaseType;
            PropertyInfo propertyInfo = mainType.GetProperty(propertyName);

            bool propertyIsBListType = propertyInfo.PropertyType.Name == typeof(BList<>).Name;


            if (byPass)
            {
                byPass = false;
                return;
            }



            if (isSetter)
            {// SETTER
                   if(propertyIsBListType )
                   {//when incoming property is list
                       Flow_BListSetter(mainObject, propertyInfo, propertyName, newValue);
                   }
                   else
                   {//when incoming property is not list
                       Flow_DataObjectSetter(mainObject, propertyInfo , ref byPass, oldValue, newValue);
                   }
            }
            else
            {// GETTER
                Flow_Getter(mainObject, propertyIsBListType, propertyInfo, ref byPass );
            }



            
        }



        //=======================================================================================
        //=======================================================================================
        //=======================================================================================
        //=======================================================================================
        //=======================================================================================

        
        private void Flow_BListSetter(IDataObject mainObject, PropertyInfo propertyInfo, string propertyName , object newValue)
        {


            Type typeArgumentOfList = Helper.GetTypeOfGenericArgument(propertyInfo.PropertyType);


            if (mainObject.Id == 0)
            {//L1


            }
            else
            {//R1

                if (DBObjectContainer.PropertyAlreadyFilledFromDatabase(mainObject, propertyName))
                {//R2

                }
                else
                {//R3
                    GenericResolver_LoadDataFromDatabaseInToInstanceContainer(mainObject, typeArgumentOfList);
                }

            }



            dynamic OriginalBList = GenericResolver_GetAssociatesFromInstanceContainer(mainObject, typeArgumentOfList);

            //========================<>======================
            IDataObject dataItem = null;
            foreach (IDataObject data in OriginalBList)
            {
                dataItem = data;
                break;
            }

            if (dataItem != null)
            {
                RelationInstance ri = RelationInstanceContainer.GetRi(mainObject, dataItem);

                if (ri.Rd.Relation == RelationSort.OneToManyComposition)
                {//R4
                    throw new CompositionException();
                }
            }
            //========================</>======================

            foreach (IDataObject data in OriginalBList)
            {
                Flow_BListSetterRelationBreaker(mainObject, data);
            }

           // invocation.Proceed();

            //byPass = true;
            dynamic NewBList = newValue; //propertyInfo.GetValue(mainObject, null);

            foreach (IDataObject data in NewBList)
            {
                Flows.Flow_BListSetterRelationCreator(mainObject, data);
            }


        }

        private void Flow_BListSetterRelationBreaker(IDataObject mainObject, IDataObject associate)
        {
            if (mainObject.Id == 0)
            {//L1

                RelationInstanceContainer.RemoveFromContainer(mainObject, associate);

            }
            else
            {//R1

                if (associate.Id == 0)
                {//R2
                    RelationInstanceContainer.RemoveFromContainer(mainObject, associate);
                }
                else
                {//R3
                    RelationInstanceContainer.BreakTheRelationShip(mainObject, associate);
                }

            }
        }
        private void Flow_DataObjectSetter(IDataObject mainObject, PropertyInfo propertyInfo , ref bool byPass , object oldValue, object newValue)
        {

//=======================this part is not in flow diagram but we need it at this point================
            IDataObject originalAssociate = null;
            IDataObject newAssociate = null;

//==================================================================================================

            if (mainObject.Id == 0)
            {//L1
                if (Helper.ImplementsIDataObjects(propertyInfo.PropertyType))
                {//L2

                    //byPass = true;
                    originalAssociate = (IDataObject)oldValue;//(IDataObject)propertyInfo.GetValue(mainObject, null);
                   
                    //invocation.Proceed();

                    //byPass = true;
                    newAssociate = (IDataObject)newValue;//(IDataObject)propertyInfo.GetValue(mainObject, null);

                    if (originalAssociate == newAssociate)
                    {
                        return;
                    }
                    
                    RelationInstanceContainer.RemoveFromContainer(mainObject, originalAssociate);
                    RelationInstanceContainer.CreateNewRelationShip(mainObject, newAssociate);

              
                }
                else
                {//L3
                    return;
                }
            }
            else
            {//R1

                if (Helper.ImplementsIDataObjects(propertyInfo.PropertyType))
                {//R5
                    if (DBObjectContainer.PropertyAlreadyFilledFromDatabase(mainObject, propertyInfo.Name))
                    {//R6

                        //byPass = true;
                        originalAssociate = (IDataObject)oldValue;//(IDataObject)propertyInfo.GetValue(mainObject, null);
                        
                        //invocation.Proceed();

                        //byPass = true;
                        newAssociate = (IDataObject)newValue;//(IDataObject)propertyInfo.GetValue(mainObject, null);

                        if (originalAssociate == newAssociate)
                        {//R9
                            return;
                        }

                        if (newAssociate == null)
                        {//R10

                            RelationInstance ri = RelationInstanceContainer.GetRi(mainObject, originalAssociate);
                            if (ri.Rd.Relation == RelationSort.OneToManyComposition)
                            {//R11
                                throw new CompositionException();
                            }
                            //R12 (will Join back to R6)
                        }

                        RelationInstanceContainer.BreakTheRelationShip(mainObject, originalAssociate);
                        RelationInstanceContainer.CreateNewRelationShip(mainObject, newAssociate);
                    }
                    else
                    {//R7
                        //Only when here, the originalvalue that we provide in beging of this methodwill be null
                        //so we have to try to get it from db.

                        GenericResolver_LoadDataFromDatabaseInToInstanceContainer(mainObject, propertyInfo.PropertyType);
                        DBObjectContainer.MarkPropertyAsFilledFromDatabase(mainObject, propertyInfo.Name);
                        originalAssociate = (IDataObject)GenericResolver_GetOneAssociateFromInstanceContainer(mainObject, propertyInfo.PropertyType);

                        //invocation.Proceed();

                        //byPass = true;
                        newAssociate = (IDataObject)newValue;//(IDataObject)propertyInfo.GetValue(mainObject, null);


                        if (originalAssociate == newAssociate)
                        {//R8
                            return;
                        }

                        if (newAssociate == null)
                        {//R13

                            RelationInstance ri = RelationInstanceContainer.GetRi(mainObject, originalAssociate);
                            if (ri.Rd.Relation == RelationSort.OneToManyComposition)
                            {//R14
                                throw new CompositionException();
                            }
                            //R15 (will Join back to R7)
                        }

                        RelationInstanceContainer.BreakTheRelationShip(mainObject, originalAssociate);
                        RelationInstanceContainer.CreateNewRelationShip(mainObject, newAssociate);


                    }
                }
                else
                {//R2

                         DBObjectContainer.MarkAsChanged(mainObject);
                        return;

                }

            }
        }

        private void Flow_Getter(IDataObject mainObject , bool propertyIsBListType , PropertyInfo propertyInfo, ref bool byPass )
        {
            object valueToBeSet = null;

            if (mainObject.Id == 0)
            {//L1

                if (propertyIsBListType)
                {//L3
                    Type AssociateType = Helper.GetTypeOfGenericArgument(propertyInfo.PropertyType);
                    valueToBeSet = GenericResolver_GetAssociatesFromInstanceContainer(mainObject, AssociateType);
                }
                else if (Helper.ImplementsIDataObjects(propertyInfo.PropertyType))
                {//L4

                    valueToBeSet = GenericResolver_GetOneAssociateFromInstanceContainer(mainObject, propertyInfo.PropertyType);
                }
                else
                {//L2
                    // do nothing
                    return;
                }

            }
            else
            {//R1

                if (propertyIsBListType)
                {//R3

                    Type listItemType = Helper.GetTypeOfGenericArgument(propertyInfo.PropertyType);

                    if (!DBObjectContainer.PropertyAlreadyFilledFromDatabase(mainObject, propertyInfo.Name))
                    {//R4
                        
                        GenericResolver_LoadDataFromDatabaseInToInstanceContainer(mainObject, listItemType);
                        DBObjectContainer.MarkPropertyAsFilledFromDatabase(mainObject, propertyInfo.Name);
                    }

                    //R5
                    valueToBeSet = GenericResolver_GetAssociatesFromInstanceContainer(mainObject, listItemType);

                }
                else if (Helper.ImplementsIDataObjects(propertyInfo.PropertyType))
                {//R6
                    if (!DBObjectContainer.PropertyAlreadyFilledFromDatabase(mainObject, propertyInfo.Name))
                    {//R7
                        GenericResolver_LoadDataFromDatabaseInToInstanceContainer(mainObject, propertyInfo.PropertyType);
                        DBObjectContainer.MarkPropertyAsFilledFromDatabase(mainObject, propertyInfo.Name);
                    }

                    //R8

                    valueToBeSet = GenericResolver_GetOneAssociateFromInstanceContainer(mainObject, propertyInfo.PropertyType);

                }
                else
                {//R2
                    return;
                }

            }

            byPass = true;
            propertyInfo.SetValue(mainObject, valueToBeSet, null);
        }
        
        //==================================================================================


        private void GenericResolver_LoadDataFromDatabaseInToInstanceContainer(IDataObject mainObject, Type AssociatedType) 
        {
            Type thisType = this.GetType();
            MethodInfo LoadDataFromDatabaseInToInstanceContainer =
                thisType.GetMethod("LoadDataFromDatabaseInToInstanceContainer").MakeGenericMethod(new Type[] { AssociatedType });

            LoadDataFromDatabaseInToInstanceContainer.Invoke(this, new object[] { mainObject });
        }

        private object GenericResolver_GetAssociatesFromInstanceContainer(IDataObject mainObject, Type AssociatedType)
        {
            Type thisType = this.GetType();
            MethodInfo AssociatesFromInstanceContainer = thisType.GetMethod("GetAssociatesFromInstanceContainer").MakeGenericMethod(new Type[] { AssociatedType });

            return AssociatesFromInstanceContainer.Invoke(this, new object[] { mainObject});
        }

        private object GenericResolver_GetOneAssociateFromInstanceContainer(IDataObject mainObject, Type AssociatedType)
        {

            Type thisType = this.GetType();
            MethodInfo OneAssociateFromInstanceContainer =
                thisType.GetMethod("GetOneAssociateFromInstanceContainer").MakeGenericMethod(new Type[] { AssociatedType });

            return OneAssociateFromInstanceContainer.Invoke(this, new object[] { mainObject });

        }

        //==================================================================================

        public void LoadDataFromDatabaseInToInstanceContainer<TAssociate>(IDataObject mainObject) where TAssociate : class, IDataObject, new()
        {//this method should be public otherwise it does not work its purpose (reflection problem will happen)

            _manager.LoadAssociates<TAssociate>(mainObject);

        }

        public BList<TAssociate> GetAssociatesFromInstanceContainer<TAssociate>(IDataObject mainObject)
        {
            BList<TAssociate> retlist = RelationInstanceContainer.GetAssociates<TAssociate>(mainObject);

            if (retlist.Count != 0)
            {
                return retlist;
            }
            else
            {
                return retlist;
            }
        }

        public TAssociate GetOneAssociateFromInstanceContainer<TAssociate>(IDataObject mainObject) where TAssociate : class
        {
            TAssociate associte = null;

            BList<TAssociate> List = RelationInstanceContainer.GetAssociates<TAssociate>(mainObject);
            if (List.Count != 0)
            {
                associte = List[0];
            }

            return associte;
        } 
   
    
    
    }
}
